package com.asgard.cert;

import com.asgard.crypto.Signable;
import com.asgard.utils.SerializeUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateException;

/**
 * 该类包装了证书的撤销。
 */
public class CertificateRevoke implements Signable {

    /**
     * 被撤销的证书SN
     */
    private BigInteger sn;
    /**
     * 被撤销的证书MEMBER_ID
     */
    private String memberID;
    /**
     * 撤销者SN
     */
    private BigInteger revokerSN;
    /**
     * 撤销者MEMBER ID
     */
    private String revokerMemberID;
    /**
     * 撤销者签名
     */
    private byte[] sigRevoker;
    /**
     * 时间戳
     */
    private long timestamp;

    /**
     * 初始化证书的撤销。
     *
     * @param sn         被撤销的证书SN
     * @param memberID   被撤销的证书MEMBER ID
     * @param revokerSN  撤销者SN
     * @param revokerMemberID 撤销者MEMBER ID
     * @param sigRevoker 撤销者签名
     * @param timestamp  时间戳
     */
    public CertificateRevoke(BigInteger sn, String memberID, BigInteger revokerSN, String revokerMemberID, byte[] sigRevoker,
                             long timestamp) {
        this.sn = sn;
        this.memberID = memberID;
        this.revokerSN = revokerSN;
        this.revokerMemberID = revokerMemberID;
        this.sigRevoker = sigRevoker;
        this.timestamp = timestamp;
    }

    /**
     * 反序列化证书撤销实例。
     *
     * @param inputStream 输入流
     * @return 证书撤销实例
     */
    public static CertificateRevoke readFromStream(InputStream inputStream) throws IOException {
        BigInteger sn = new BigInteger(SerializeUtils.readByteArray(inputStream));
        String memberID = SerializeUtils.readString(inputStream);
        BigInteger revokerSN = new BigInteger(SerializeUtils.readByteArray(inputStream));
        String revokerMemberID = SerializeUtils.readString(inputStream);
        byte[] sigRevoker = SerializeUtils.readByteArray(inputStream);
        long timestamp = SerializeUtils.readLong(inputStream);
        return new CertificateRevoke(sn, memberID, revokerSN, revokerMemberID, sigRevoker, timestamp);
    }

    /**
     * 反序列化证书撤销实例。
     *
     * @param bytes 字节数组
     * @return 证书撤销实例
     */
    public static CertificateRevoke readFromBytes(byte[] bytes) {
        try (ByteArrayInputStream bin = new ByteArrayInputStream(bytes)) {
            return readFromStream(bin);
        } catch (IOException e) {
            // cannot happen
            return null;
        }
    }

    /**
     * 获取用于签名的证书撤销字节数组。
     *
     * @return 用于签名的证书撤销字节数组
     */
    public byte[] getSignable() {
        // 签名字节数组序列化顺序
        // 前缀标识,MEMBER_ID,SN,撤销者MEMBER_ID,撤销者SN,撤销时间,逗号分隔
        StringBuilder sb = new StringBuilder();
        sb.append("certificate revoke: ");
        sb.append(memberID).append(',').append(getSN().toString()).append(',');
        sb.append(revokerMemberID).append(',').append(getRevokerSN().toString()).append(',');
        sb.append("" + timestamp);
        return sb.toString().getBytes();
    }

    /**
     * 校验证书的撤销，注意该方法不校验其他证书撤销。
     *
     * @param caCert       可信根证书
     * @param thisCert     被撤销的证书
     * @param certificates 中间证书，顺序为从低到高
     * @throws InvalidAlgorithmParameterException 证书算法参数无效
     * @throws NoSuchAlgorithmException           证书算法无效
     * @throws CertificateException               证书不正确
     * @throws CertPathValidatorException         证书路径不正确
     */
    public void validate(Certificate caCert, Certificate thisCert, Certificate... certificates)
            throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, CertificateException,
            CertPathValidatorException {
        // 校验被撤销的证书的正确性
        if (!thisCert.getSN().equals(sn))
            throw new CertificateException("CertificateRevoke: SN doesn't match.");
        if (!thisCert.getMemberID().equals(memberID))
            throw new CertificateException("CertificateRevoke: MEMBER ID doesn't match.");
        // 检验证书链的正确性
        thisCert.validate(this.timestamp, caCert, certificates);
        // 在证书链中寻找撤销者
        Certificate revokerFound = null;
        byte[] signable = this.getSignable();
        if (caCert.getMemberID().equals(revokerMemberID) && caCert.getSN().equals(revokerSN)) {
            revokerFound = caCert;
        } else {
            for (Certificate cert : certificates) {
                if (cert.getMemberID().equals(revokerMemberID) && cert.getSN().equals(revokerSN)) {
                    revokerFound = cert;
                    break;
                }
            }
        }
        if (revokerFound == null)
            throw new CertificateException("CertificateRevoke: cannot find revoker");
        if (!revokerFound.verify(signable, sigRevoker))
            throw new CertificateException("CertificateRevoke: signature not valid");
    }

    /**
     * 序列化证书撤销实例。
     *
     * @return 字节数组
     */
    public byte[] writeToBytes() {
        try (ByteArrayOutputStream bout = new ByteArrayOutputStream()) {
            writeToStream(bout);
            return bout.toByteArray();
        } catch (IOException e) {
            // cannot happen
            return null;
        }
    }

    /**
     * 序列化证书撤销实例。
     *
     * @param sout 输出流
     * @throws IOException 输入输出异常
     */
    public void writeToStream(OutputStream sout) throws IOException {
        SerializeUtils.writeByteArray(sout, sn.toByteArray());
        SerializeUtils.writeString(sout, memberID);
        SerializeUtils.writeByteArray(sout, revokerSN.toByteArray());
        SerializeUtils.writeString(sout, revokerMemberID);
        SerializeUtils.writeByteArray(sout, sigRevoker);
        SerializeUtils.writeLong(sout, timestamp);
    }

    /**
     * 获取被撤销证书的SN。
     *
     * @return 被撤销证书的SN
     */
    public BigInteger getSN() {
        return sn;
    }

    /**
     * 获取被撤销证书的MEMBER_ID。
     *
     * @return 被撤销证书的MEMBER_ID
     */
    public String getMemberID() {
        return memberID;
    }

    /**
     * 获取证书撤销者的SN。
     *
     * @return 证书撤销者的SN
     */
    public BigInteger getRevokerSN() {
        return revokerSN;
    }

    /**
     * 获取证书撤销者的MEMBER_ID。
     *
     * @return 证书撤销者的MEMBER_ID
     */
    public String getRevokerMemberID() {
        return revokerMemberID;
    }

    /**
     * 获取证书撤销者的签名。
     *
     * @return 证书撤销者的签名
     */
    public byte[] getSigRevoker() {
        return sigRevoker;
    }

    /**
     * 获取时间戳。
     *
     * @return 时间戳
     */
    public long getTimestamp() {
        return timestamp;
    }

    /**
     * 设置证书撤销者的签名。
     *
     * @param sigRevoker 证书撤销者的签名
     */
    public void setSigRevoker(byte[] sigRevoker) {
        this.sigRevoker = sigRevoker;
    }

}
